home *** CD-ROM | disk | FTP | other *** search
- /*----------------------------------------------------------------------------
-
- news.c
-
- This module handles all transactions with the NNTP server. It acts as
- an interface between the rest of NewsWatcher and the resuable nntp.c
- module.
-
- Copyright © 1994-1995, Northwestern University.
-
- ----------------------------------------------------------------------------*/
-
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
-
- #include "glob.h"
- #include "dialog.h"
- #include "news.h"
- #include "nntp.h"
- #include "status.h"
- #include "newswatcher.h"
- #include "charset.h"
- #include "text.h"
- #include "strutil.h"
- #include "memutil.h"
- #include "cache.h"
- #include "windutil.h"
- #include "resutil.h"
- #include "fileutil.h"
- #include "ic.h"
-
-
-
- #define kPostAuthDlg 136
- #define kStartupAuthDlg 137
- #define kAuthUsername 5
- #define kAuthPassword 7
-
- #define kFileBufLen (10*1024)
-
-
-
- typedef struct TTruncateAttachedFileInfo {
- long pos; /* current position in text received so far */
- Boolean flagReqd; /* true if "flag" line required */
- } TTruncateAttachedFileInfo;
-
- typedef struct TCopyArticleToFileInfo {
- short fileRefNum; /* file ref num */
- Ptr fileBuf; /* pointer to output buffer */
- Ptr fileBufAux; /* pointer to auxiliary output buffer */
- long fileBufAuxSize; /* number of bytes in aux buffer */
- long fileBufPos; /* current position in output buffer */
- long fileLinePos; /* position in current line */
- char fileLastChar; /* last char */
- Boolean fileTruncate; /* true to truncate if attached file */
- Boolean fileTruncated; /* true if attached file found, should truncate */
- TAttachedFileKind fileKind; /* kind of attached file */
- Boolean flagReqd; /* true if attached file must include "begin" flag line */
- } TCopyArticleToFileInfo;
-
-
-
- static NntpStreamRef gNewsStream = nil; /* reference to news server NNTP stream */
-
-
-
- /*----------------------------------------------------------------------------
- IsLegalBinHexLine
-
- Check a line to see if it contains legal BinHex encoded text.
-
- Entry: p = pointer to line.
- len = length of line.
-
- Exit: function result = true if legal BinHex.
- ----------------------------------------------------------------------------*/
-
- static Boolean IsLegalBinHexLine (unsigned char *p, long len)
- {
- unsigned char *pEnd, *pBegin;
- unsigned char c;
-
- static Boolean legalBinHexChar[] = {
- /* 0x00 */
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- /* 0x10 */
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- /* 0x20 */
- false, true, true, true, true, true, true, true,
- true, true, true, true, true, true, false, false,
- /* 0x30 */
- true, true, true, true, true, true, true, false,
- true, true, true, false, false, false, false, false,
- /* 0x40 */
- true, true, true, true, true, true, true, true,
- true, true, true, true, true, true, true, false,
- /* 0x50 */
- true, true, true, true, true, true, true, false,
- true, true, true, true, false, false, false, false,
- /* 0x60 */
- true, true, true, true, true, true, true, false,
- true, true, true, true, true, true, false, false,
- /* 0x70 */
- true, true, true, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- /* 0x80 */
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- false, false, false, false, false, false, false, false,
- };
-
- for (pBegin = p, pEnd = p + len; p < pEnd; p++)
- if (!legalBinHexChar[*p]) return false;
- c = *pBegin;
- for (p = pBegin+1; p < pEnd; p++)
- if (*p != c) return true;
- return false;
- }
-
-
-
- /*----------------------------------------------------------------------------
- IsLegalUULine
-
- Check a line to see if it contains legal uuencode text.
-
- Entry: p = pointer to line.
- len = length of line.
-
- Exit: function result = true if legal uuencode.
- ----------------------------------------------------------------------------*/
-
- static Boolean IsLegalUULine (unsigned char *p, long len)
- {
- long n;
- unsigned char *pEnd, *pBegin;
- unsigned char c;
-
- n = *p - ' ';
- if (n < 0 || n > 63) return false;
- n = (n+2)/3*4;
- if (len > n+3 || len < n-3) return false;
- for (pBegin = p, pEnd = p + len, p++; p < pEnd; p++)
- if (*p < ' ' || *p > '`') return false;
- c = *pBegin;
- for (p = pBegin+1; p < pEnd; p++)
- if (*p != c) return true;
- return false;
- }
-
-
-
- /*----------------------------------------------------------------------------
- CheckForAttachedFile
-
- Check for BinHex or UUEncode text.
-
- Entry: t = pointer to text.
- len = length of text.
- flagReqd = true if the encoded text must include a begin "flag" line.
- skipToNextLine = true to skip to beginning of next line.
-
- Exit: function result = true if BinHex or UUEncode text encountered.
- *pos = offset in text of beginning of BinHex or UUEncode text if
- function result is true, else offset in text of location where
- search should be resumed when more text has arrived from the
- server.
-
- The text can have either CR or CRLF line terminators.
-
- This function looks for the flag line (if required), followed by
- two adjacent lines of the same length at least 60 characters long,
- each of which is either a legal BinHex line or a legal uuencode line.
- ----------------------------------------------------------------------------*/
-
- static Boolean CheckForAttachedFile (Ptr t, long len, Boolean flagReqd,
- Boolean skipToNextLine, long *pos)
- {
- long lenA, lenB;
- unsigned char *p, *pEnd, *q, *r;
- unsigned char *flagLine, *lineA, *lineB, *nextLine;
- Boolean haveBeginFlagLine, beginFlagLineIsBinHex, foundIt;
-
- p = (unsigned char*)t;
- pEnd = p + len - 1;
-
- if (skipToNextLine) {
- while (p < pEnd && *p != CR) p++;
- while (p < pEnd && (*p == CR || *p == LF)) p++;
- if (p >= pEnd) return false;
- }
-
- while (p < pEnd) {
- if (flagReqd) {
- haveBeginFlagLine = false;
- flagLine = p;
- while (flagLine < pEnd) {
- q = flagLine;
- while (q < pEnd && *q != CR) q++;
- while (q < pEnd && (*q == CR || *q == LF)) q++;
- if (q >= pEnd) break;
- if (*flagLine == '(') {
- if (flagLine + 39 >= pEnd) break;
- if (strncmp((char*)flagLine,
- "(This file must be converted with BinHex", 39) == 0)
- {
- haveBeginFlagLine = true;
- beginFlagLineIsBinHex = true;
- break;
- }
- } else if (*flagLine == 'b') {
- if (flagLine + 9 >= pEnd) break;
- if (strncmp((char*)flagLine, "begin ", 6) == 0) {
- r = flagLine + 6;
- if (isoctal(*r) && isoctal(*(r+1)) && isoctal(*(r+2))) {
- haveBeginFlagLine = true;
- beginFlagLineIsBinHex = false;
- break;
- }
- }
- }
- flagLine = q;
- }
- if (!haveBeginFlagLine) {
- *pos = (char*)flagLine - t;
- return false;
- }
- p = flagLine;
- lineA = q;
- if (lineA >= pEnd) break;
- } else {
- lineA = p;
- }
- q = lineA;
- while (q < pEnd && *q != CR) q++;
- lenA = q - lineA;
- while (q < pEnd && (*q == CR || *q == LF)) q++;
- lineB = q;
- if (lineB >= pEnd) break;
- nextLine = flagReqd ? lineA : lineB;
- if (lenA < 60) {
- p = nextLine;
- continue;
- }
- q = lineB;
- while (q < pEnd && *q != CR) q++;
- if (q >= pEnd) break;
- lenB = q - lineB;
- if (lenA != lenB) {
- p = nextLine;
- continue;
- }
- if (flagReqd) {
- if (beginFlagLineIsBinHex) {
- foundIt = IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB);
- } else {
- foundIt = IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB);
- }
- } else {
- foundIt = (IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB)) ||
- (IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB));
- }
- if (foundIt) {
- *pos = (char*)lineA - t;
- return true;
- } else {
- p = nextLine;
- continue;
- }
- }
-
- *pos = (char*)p - t;
- return false;
- }
-
-
-
- /*----------------------------------------------------------------------------
- TruncateAttachedFile
-
- Check for attached file and truncate article if BinHex or uuencode
- text encountered.
-
- Entry: t = pointer to raw text received from server so far.
- tLen = length of text received from server so far.
- *userDataPtr = pointer to TTruncateAttachedFileInfo struct.
-
- Exit: function result = error code = netTruncatedErr if attached
- file discovered.
- *truncPos = position at which text should be truncated
- if error code = netTruncatedErr.
- ----------------------------------------------------------------------------*/
-
- static OSErr TruncateAttachedFile (Ptr t, long tLen, Ptr userDataPtr,
- long *truncPos)
- {
- TTruncateAttachedFileInfo *x;
- Boolean flagReqd;
- long pos;
- Boolean foundIt;
-
- x = (TTruncateAttachedFileInfo*)userDataPtr;
- flagReqd = x->flagReqd;
- pos = x->pos;
- if (pos >= tLen) return noErr;
-
- foundIt = CheckForAttachedFile(t + pos, tLen - pos, flagReqd, false, &pos);
-
- pos += x->pos;
- if (foundIt) {
- *truncPos = pos;
- return netTruncatedErr;
- } else {
- x->pos = pos;
- return noErr;
- }
- }
-
-
-
- /*----------------------------------------------------------------------------
- NetworkError
-
- Process a network error.
-
- Entry: stream = reference to NNTP stream.
- host = address of news server, or nil if default server.
- err = error code as returned by function in nntp.c.
-
- Entry: function result = error code.
-
- If the error is a server error, the error is reported to the user,
- and the function result is userCanceledErr. Otherwise, the error
- code is returned as is, and it is the responsibility of the calling
- code to report it.
- ----------------------------------------------------------------------------*/
-
- static OSErr NetworkError (NntpStreamRef stream, char *host, OSErr err)
- {
- NetServerErrInfo serverErrInfo;
-
- if (err == nntpServerErr) {
- NntpGetServerErrInfo(stream, &serverErrInfo);
- err = ServerErrorMessage(kStrNews, serverErrInfo.command, serverErrInfo.response);
- if (err != noErr) return err;
- return userCanceledErr;
- } else {
- if (host == nil) {
- p2cstr(gPrefs.newsServerName);
- SaveNetErrorInfo(kStrNews, (char*)gPrefs.newsServerName);
- c2pstr((char*)gPrefs.newsServerName);
- } else {
- SaveNetErrorInfo(kStrNews, host);
- }
- return err;
- }
- }
-
-
-
- /*----------------------------------------------------------------------------
- SetServerOptions
-
- Set news server options from preferences.
-
- Exit: *options = current server options.
- ----------------------------------------------------------------------------*/
-
- static void SetServerOptions (NntpStreamOptions *options)
- {
- MyICReadSharedPrefs(kICNewsAuthUsername);
- MyICReadSharedPrefs(kICNewsAuthPassword);
-
- options->idleTime = 10;
- options->useXPAT = gPrefs.useXPAT;
- options->sendModeReader = !gPrefs.noModeReader;
- options->batchedCmds = gPrefs.batchedGroupCmds;
- options->newConnection = !gPrefs.noNewConnection;
- options->authOnConnect = gPrefs.authAtStartup;
- strcpy(options->username, gPrefs.authUsername);
- strcpy(options->password, gPrefs.authPassword);
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetAuthInfo
-
- Get authorization information.
-
- Entry: dlgID = dialog id.
- statusMsg = status message, C-format, or nil if startup call.
-
- Exit: function result = error code.
- gPrefs.authUsername = authorization username.
- gPrefs.authPassword = authorization password.
- ----------------------------------------------------------------------------*/
-
- static OSErr GetAuthInfo (short dlgID, char *statusMsg)
- {
- DialogPtr dlg = nil;
- short item;
- CStr255 tempStr;
- short len;
- char username[32];
- char password[32];
- OSErr err = noErr;
-
- MyICReadSharedPrefs(kICNewsAuthUsername);
- MyICReadSharedPrefs(kICNewsAuthPassword);
-
- if (*gPrefs.authUsername != 0 && *gPrefs.authPassword != 0) return noErr;
-
- err = MyGetNewDialog(dlgID, ok, cancel, &dlg);
- if (err != noErr) return err;
- RestoreMovableModalDialogPosition(dlg, gPrefs.authLoc);
- strcpy(username, gPrefs.authUsername);
- DlgSetCString(dlg, kAuthUsername, username);
- SetItemUSAsciiNoBlank(dlg, kAuthUsername);
- SetItemMaxLength(dlg, kAuthUsername, 31);
- strcpy(password, gPrefs.authPassword);
- len = strlen(password);
- memset(tempStr, '•', len);
- tempStr[len] = 0;
- DlgSetCString(dlg, kAuthPassword, tempStr);
- SetItemPassword(dlg, kAuthPassword, password);
- SetItemMaxLength(dlg, kAuthPassword, 31);
- if (*username == 0) {
- SelectDialogItemText(dlg, kAuthUsername, 0, 0);
- } else if (*password == 0) {
- SelectDialogItemText(dlg, kAuthPassword, 0, 0);
- } else {
- SelectDialogItemText(dlg, kAuthUsername, 0, 0x7fff);
- }
-
- do {
- DlgEnableItem(dlg, ok, *username != 0 && *password != 0);
- MyMovableModalDialog(dlg, DialogFilter, &item);
- if (item == kAuthUsername) DlgGetCString(dlg, item, username);
- } while (item != ok && item != cancel);
-
- if (item == ok) {
- strcpy(gPrefs.authUsername, username);
- strcpy(gPrefs.authPassword, password);
- MyICWriteSharedPrefs(kICNewsAuthUsername);
- if (gPrefs.authSavePassword) MyICWriteSharedPrefs(kICNewsAuthPassword);
- }
- SaveMovableModalDialogPosition(dlg, &gPrefs.authLoc);
- err = DoClose(dlg);
- if (err != noErr) return err;
- if (item == ok && statusMsg != nil) {
- err = DisplayStatusMessage(statusMsg);
- if (err != noErr) return err;
- }
- return item == ok ? noErr : userCanceledErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- TextHasAttachedFile
-
- Check article text to see if it includes an attached file.
-
- Entry: text = pointer to article text.
- length = length of text.
-
- Exit: *fileKind = kind of attached file.
- ----------------------------------------------------------------------------*/
-
- static void TextHasAttachedFile (Ptr text, long length, TAttachedFileKind *fileKind)
- {
- char *p, *pEnd, *q;
-
- for (p = text, pEnd = p + length; p < pEnd; p++) {
- if (*p == CR) {
- q = p+1;
- if (q >= pEnd) break;
- if (*q == '(') {
- if (strncmp(q, "(This file must be converted with BinHex", 39) == 0) {
- *fileKind = kBinHex;
- return;
- }
- } else if (*q == 'b') {
- if (strncmp(q, "begin ", 6) == 0) {
- q += 6;
- if (q >= pEnd) break;
- if (isoctal(*q) && isoctal(*(q+1)) && isoctal(*(q+2))) {
- *fileKind = kUUEncode;
- return;
- }
- }
- }
- }
- }
- *fileKind = kNoAttachedFile;
- }
-
-
-
- /*----------------------------------------------------------------------------
- FlushFileBuf
-
- Flush the file buffer.
-
- Entry: len = length of buffer.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- static OSErr FlushFileBuf (long len, TCopyArticleToFileInfo *x)
- {
- OSErr err = noErr;
- long headLen, tailLen, pos;
-
- MapLatin1ToMacPtr(x->fileBuf, len);
-
- if (x->fileTruncate) {
-
- if (x->fileBufAuxSize > 0) {
- headLen = len > 200 ? 200 : len;
- BlockMoveData(x->fileBuf, x->fileBufAux + x->fileBufAuxSize, headLen);
- x->fileBufAuxSize += headLen;
- if (CheckForAttachedFile(x->fileBufAux, x->fileBufAuxSize, x->flagReqd, true, &pos)) {
- if (pos > 0) {
- err = MyFSWriteNoCache(x->fileRefNum, &pos, x->fileBufAux, GiveTime);
- if (err != noErr) return err;
- }
- x->fileTruncated = true;
- return noErr;
- }
- x->fileBufAuxSize -= headLen;
- err = MyFSWriteNoCache(x->fileRefNum, &x->fileBufAuxSize, x->fileBufAux, GiveTime);
- if (err != noErr) return err;
- }
- if (CheckForAttachedFile(x->fileBuf, len, x->flagReqd, true, &pos)) {
- if (pos > 0) {
- err = MyFSWriteNoCache(x->fileRefNum, &pos, x->fileBuf, GiveTime);
- if (err != noErr) return err;
- }
- x->fileTruncated = true;
- return noErr;
- }
- tailLen = len > 200 ? 200 : len;
- len -= tailLen;
- BlockMoveData(x->fileBuf + len, x->fileBufAux, tailLen);
- x->fileBufAuxSize = tailLen;
- if (len > 0) {
- err = MyFSWriteNoCache(x->fileRefNum, &len, x->fileBuf, GiveTime);
- if (err != noErr) return err;
- }
-
- } else {
-
- err = MyFSWriteNoCache(x->fileRefNum, &len, x->fileBuf, GiveTime);
- if (err != noErr) return err;
- if (x->fileKind == kNoAttachedFile) {
- if (x->fileBufAuxSize > 0) {
- headLen = len > 200 ? 200 : len;
- BlockMoveData(x->fileBuf, x->fileBufAux + x->fileBufAuxSize, headLen);
- x->fileBufAuxSize += headLen;
- TextHasAttachedFile(x->fileBufAux, x->fileBufAuxSize, &x->fileKind);
- }
- if (x->fileKind == kNoAttachedFile)
- TextHasAttachedFile(x->fileBuf, len, &x->fileKind);
- if (x->fileKind == kNoAttachedFile) {
- tailLen = len > 200 ? 200 : len;
- BlockMoveData(x->fileBuf + len - tailLen, x->fileBufAux, tailLen);
- x->fileBufAuxSize = tailLen;
- }
- }
-
- }
-
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- CopyArticleToFileChunkFunction
-
- Copy one article chunk to a file.
-
- Entry: t = pointer to chunk.
- tLen = length of chunk.
- userDataPtr = pointer to TCopyArticleToFileInfo struct.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- static OSErr CopyArticleToFileChunkFunction (Ptr t, long tLen,
- Ptr userDataPtr, long *unused)
- {
- register char c, d;
- register Ptr p;
- Ptr pEnd;
- short nSpace;
- OSErr err = noErr;
- TCopyArticleToFileInfo *x;
-
- x = (TCopyArticleToFileInfo*)userDataPtr;
-
- if (tLen == 0) return noErr;
-
- if (x->fileLastChar != 0) {
- c = x->fileLastChar;
- } else {
- c = *t;
- t++;
- tLen--;
- }
-
- p = x->fileBuf + x->fileBufPos;
- pEnd = x->fileBuf + kFileBufLen - 10;
-
- while (tLen > 0) {
- if (p >= pEnd) {
- err = FlushFileBuf(p - x->fileBuf, x);
- if (err != noErr) return err;
- if (x->fileTruncated) return netTruncatedErr;
- p = x->fileBuf;
- }
- d = *t;
- if (c == BS && d == '_' || c == '_' && d == BS) {
- t++;
- c = *t;
- t++;
- tLen -= 2;
- } else if (c == CR && d == LF) {
- *p++ = CR;
- t++;
- c = *t;
- t++;
- tLen -= 2;
- x->fileLinePos = 0;
- } else if (x->fileLinePos == 0 && c == '.' && d == '.') {
- *p++ = '.';
- t++;
- c = *t;
- t++;
- tLen -= 2;
- x->fileLinePos = 1;
- } else if (c == '\t') {
- nSpace = 8 - (x->fileLinePos % 8);
- while (nSpace--) *p++ = ' ';
- x->fileLinePos += nSpace;
- c = d;
- t++;
- tLen--;
- } else if (c >= ' ' || c == CR || c == FF || c < 0) {
- *p++= c;
- x->fileLinePos++;
- c = d;
- t++;
- tLen--;
- } else {
- c = d;
- t++;
- tLen--;
- }
- }
-
- if (tLen == 0) {
- x->fileLastChar = c;
- } else {
- x->fileLastChar = 0;
- }
- x->fileBufPos = p - x->fileBuf;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- BuildXPAT
-
- Build a regular expression string for an XPAT command.
-
- Entry: pattern = substring for which we are searching.
- regExpLen = max length of regular expression.
-
- Exit: regExp = regular expression.
- ----------------------------------------------------------------------------*/
-
- static void BuildXPAT (char *pattern, char *regExp, short regExpLen)
- {
- unsigned char *p, *q, *qEnd, *e;
- unsigned char equivs[256];
- short numEquiv;
-
- p = (unsigned char*)pattern;
- q = (unsigned char*)regExp;
- qEnd = q + regExpLen;
-
- if (q >= qEnd) goto exit;
- *q++ = '*';
- while (*p != 0) {
- GetEquivalentCharacters(*p, equivs, &numEquiv);
- if (numEquiv > 1) {
- if (q >= qEnd) goto exit;
- *q++ = '[';
- }
- e = equivs;
- while (*e != 0) {
- if (*e == '*' || *e == '?' || *e == '\\' || *e == '[' || *e == ']') {
- if (q >= qEnd) goto exit;
- *q++ = '\\';
- }
- if (q >= qEnd) goto exit;
- *q++ = *e++;
- }
- if (numEquiv > 1) {
- if (q >= qEnd) goto exit;
- *q++ = ']';
- }
- p++;
- }
- if (q >= qEnd) goto exit;
- *q++ = '*';
- if (q >= qEnd) goto exit;
- *q = 0;
- MapMacToLatin1Ptr(regExp, (char*)q - regExp);
- return;
-
- exit:
-
- *(regExp + regExpLen - 1) = 0;
- return;
- }
-
-
-
- /*----------------------------------------------------------------------------
- MatchPattern
-
- Match a pattern.
-
- Entry: pattern = substring for which we are searching, in Mac 8 bit
- character set.
- string = string to be searched, in Latin-1 8 bit character set.
-
- Exit: function result = true if string contains pattern.
- ----------------------------------------------------------------------------*/
-
- static Boolean MatchPattern (char *pattern, char *string)
- {
- CStr255 str;
-
- MapLatin1ToMacStr(string, str);
- return MyIsASubstring(str, pattern);
- }
-
-
-
- /*----------------------------------------------------------------------------
- StartNNTP
-
- Open a connection to the news server.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- OSErr StartNNTP (void)
- {
- NntpStreamOptions options;
- OSErr err = noErr;
-
- gCancel = false;
-
- MyICReadSharedPrefs(kICNNTPHost);
-
- while (true) {
- gNewsStream = nil;
- if (gPrefs.authAtStartup) {
- err = GetAuthInfo(kStartupAuthDlg, nil);
- if (err != noErr) return err;
- }
- err = DisplayStatusMessageNumber(kStrOpeningConnectionStatusMsg);
- if (err != noErr) return err;
- SetServerOptions(&options);
- p2cstr(gPrefs.newsServerName);
- err = NntpOpen((char*)gPrefs.newsServerName, &options, &gNewsStream);
- c2pstr((char*)gPrefs.newsServerName);
- if (gPrefs.authAtStartup && err == nntpAuthFailedErr) {
- *gPrefs.authPassword = 0;
- MyICWriteSharedPrefs(kICNewsAuthPassword);
- gNewsStream = nil;
- ErrorMessageNumber(kStrAuthFailed);
- } else if (err != noErr) {
- goto exit;
- } else {
- break;
- }
- }
- return noErr;
-
- exit:
-
- err = NetworkError(gNewsStream, nil, err);
- if (gNewsStream != nil) NntpClose(gNewsStream);
- gNewsStream = nil;
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- EndNNTP
-
- Close the connection to the NNTP server.
- ----------------------------------------------------------------------------*/
-
- void EndNNTP (void)
- {
- if (gNewsStream != nil) NntpClose(gNewsStream);
- gNewsStream = nil;
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetGroupNames
-
- Get a list of group names from the server.
-
- Entry: lastTime = 0: Fetch the entire full group list.
- lastTime != 0: Fetch just the groups which have been created
- since lastTime - 36 hours.
-
- Exit: function result = error code.
- *strings = handle to C-format group name strings.
- *numGroups = number of group names.
-
- The group names are mapped from Latin-1 to the Mac character set.
- The group names are truncated to 127 characters.
- Groups with status 'x' and '=' are filtered out (not returned).
- ----------------------------------------------------------------------------*/
-
- OSErr GetGroupNames (unsigned long lastTime, Handle *strings, long *numGroups)
- {
- OSErr err;
- Handle theStrings;
- long theNumGroups;
-
- if (lastTime != 0) {
-
- /* Just get groups added since "lastTime". Subtract 36 hours to compensate
- for clock drift, daylight savings time, and server and client in
- different time zones. */
-
- lastTime -= 60L*60L*36L;
- }
-
- err = NntpGetGroupNames(gNewsStream, lastTime, "x=",
- &theStrings, &theNumGroups);
- if (err != noErr) return NetworkError(gNewsStream, nil, err);
-
- MapLatin1ToMacHandle(theStrings);
- *strings = theStrings;
- *numGroups = theNumGroups;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetArticle
-
- Get one article from an NNTP server.
-
- Entry: host = address of news server, or nil to use default server.
- port = port number on news server if host not nil.
- groupName = name of group, or nil if fetching by message id.
- number = article number. Ignored if fetching by message id.
- id = message id string, including < and > delimiters. Ignored
- if fetching by article number.
- part = which part of the article to get:
- "ARTICLE": full article text, header and body.
- "HEAD": only article header.
- "BODY": only article body.
- truncateIfAttachedFile = true to truncate the article if it
- contains an attached file. The BinHex or uuencode text
- for the attached file is not returned.
- requireEncodedTextBeginLIne = true if BinHex or uuencode text
- must include special "begin" flag line.
-
- Exit: function result = error code.
- *text = handle to article text, or nil if article does not
- exit on server or group does not exist.
- *textLength = length of article text.
- *attachedFile = true if article contains an attached file which
- was truncated.
-
- Backspace-underscore and underscore-backspace sequences are filtered out
- of the article, as are all non-printable characters except for TAB and CR and FF.
- Tabs are expanded to 8 column tab stops.
- Trailing blank lines are deleted.
- CRLF line terminators are mapped to CR.
- Leading double-period characters on lines are mapped to single periods.
- 8 bit characters are mapped from Latin-1.
- ----------------------------------------------------------------------------*/
-
- OSErr GetArticle (char *host, short port,
- char *groupName, long number, char *id, char *part,
- Boolean truncateIfAttachedFile, Boolean flagReqd,
- Handle *text, long *textLength, Boolean *attachedFile)
- {
- long len, newLen, col, nSpace;
- unsigned char **txt = nil;
- unsigned char *p, *pEnd, *q, *r;
- OSErr err = noErr;
- NntpStreamRef stream = nil;
- CStr255 hostAndPortString;
- TTruncateAttachedFileInfo x;
-
- if (host == nil) {
- stream = gNewsStream;
- } else {
- sprintf(hostAndPortString, "%s,%d", host, port);
- err = NntpOpen(hostAndPortString, nil, &stream);
- if (err != noErr) goto exit;
- }
-
- if (truncateIfAttachedFile) {
- x.pos = 0;
- x.flagReqd = flagReqd;
- err = NntpGetArticle(stream, groupName, number, id, part,
- (Handle*)&txt, TruncateAttachedFile, (Ptr)&x);
- if (err == netTruncatedErr) {
- if (attachedFile != nil) *attachedFile = true;
- } else {
- if (attachedFile != nil) *attachedFile = false;
- if (err != noErr) goto exit;
- }
- } else {
- err = NntpGetArticle(stream, groupName, number, id, part,
- (Handle*)&txt, nil, nil);
- if (err != noErr) goto exit;
- }
-
- if (host != nil) {
- NntpClose(stream);
- stream = nil;
- }
-
- len = MyGetHandleSize(txt);
-
- for (p = *txt, pEnd = p + len, q = *txt; p < pEnd;) {
- if (p < pEnd-1 && ((*p == BS && *(p+1) == '_') || (*p == '_' && *(p+1) == BS))) {
- /* Filter underscore backspace and backspace underscore */
- p += 2;
- } else if (*p >= ' ' || *p == CR || *p == '\t' || *p == FF) {
- /* Copy printable character as is */
- *q++ = *p++;
- } else {
- /* Filter unprintable character */
- p++;
- }
- }
-
- /* Trim trailing blank lines */
-
- q--;
- while (q >= *txt && *q == CR) q--;
- q++;
-
- len = q - *txt;
- MySetHandleSize(txt, len);
-
- /* Map Latin1 */
-
- MapLatin1ToMacHandle((Handle)txt);
-
- /* Expand tabs. */
-
- newLen = len;
- r = *txt;
- for (p = *txt, pEnd = p + len; p < pEnd; p++) {
- if (*p == '\t') {
- col = p - r;
- nSpace = 8 - (col % 8);
- newLen += nSpace - 1;
- r = p+1;
- } else if (*p == CR) {
- r = p+1;
- }
- }
- if (newLen > len) {
- err = MySetHandleSize(txt, newLen);
- if (err != noErr) goto exit;
- BlockMoveData(*txt, *txt + newLen - len, len);
- r = *txt + newLen - len;;
- for (p = r, pEnd = p + len, q = *txt; p < pEnd; p++) {
- if (*p == '\t') {
- col = p - r;
- nSpace = 8 - (col % 8);
- while (nSpace--) *q++ = ' ';
- r = p+1;
- } else if (*p == CR) {
- *q++ = *p;
- r = p+1;
- } else {
- *q++ = *p;
- }
- }
- len = newLen;
- }
-
- *text = (Handle)txt;
- *textLength = len;
- return noErr;
-
- exit:
-
- if (err == nntpNoSuchGroupErr || err == nntpNoSuchArticleErr) {
- *text = nil;
- return noErr;
- } else {
- err = NetworkError(stream, host, err);
- }
- if (host != nil && stream != nil) NntpClose(stream);
- MyDisposeHandle(txt);
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- CopyArticleToFile
-
- Get one article from the NNTP server and copy it to a file.
-
- Entry: groupName = name of group, or nil if fetching by message id.
- number = article number. Ignored if fetching by message id.
- id = message id string, including < and > delimiters. Ignored
- if fetching by article number.
- part = which part of the article to get:
- "ARTICLE": full article text, header and body.
- "HEAD": only article header.
- "BODY": only article body.
- refNum = reference number of open file.
- truncateIfAttachedFile = true to truncate the article if it
- contains an attached file. The BinHex or uuencode text
- for the attached file is not copied to the file.
- flagReqd = true if BinHex or uuencode text
- must include special "begin" flag line.
-
- Exit: function result = error code.
- *fileKind = attached file kind, if !truncateIfAttachedFile.
-
- Backspace-underscore and underscore-backspace sequences are filtered out
- of the article, as are all non-printable characters except for TAB and CR and FF.
- Tabs are expanded to 8 column tab stops.
- CRLF line terminators are mapped to CR.
- Leading double-period characters on lines are mapped to single periods.
- 8 bit characters are mapped from Latin-1.
- ----------------------------------------------------------------------------*/
-
- OSErr CopyArticleToFile (char *groupName, long number, char *id, char *part,
- short refNum, Boolean truncateIfAttachedFile,
- Boolean flagReqd, TAttachedFileKind *fileKind)
- {
- OSErr err = noErr;
- TCopyArticleToFileInfo x;
-
- x.fileBuf = x.fileBufAux = nil;
- x.fileRefNum = refNum;
- err = MyNewPtr(kFileBufLen, &x.fileBuf);
- if (err != noErr) goto exit;
- err = MyNewPtr(400, &x.fileBufAux);
- if (err != noErr) goto exit;
- x.fileBufAuxSize = 0;
- x.fileBufPos = 0;
- x.fileLinePos = 0;
- x.fileLastChar = 0;
- x.fileTruncate = truncateIfAttachedFile;
- x.fileTruncated = false;
- x.fileKind = kNoAttachedFile;
- x.flagReqd = flagReqd;
-
- err = NntpGetArticle(gNewsStream, groupName, number, id, part,
- nil, CopyArticleToFileChunkFunction, (Ptr)&x);
- if (err == netTruncatedErr) err = noErr;
- if (err != noErr) goto exit;
-
- if (!x.fileTruncated) {
- if (x.fileLastChar != 0) {
- *(x.fileBuf + x.fileBufPos) = x.fileLastChar;
- x.fileBufPos++;
- }
- if (x.fileBufPos > 0) err = FlushFileBuf(x.fileBufPos, &x);
- if (x.fileTruncate && x.fileBufAuxSize > 0) {
- err = MyFSWriteNoCache(x.fileRefNum, &x.fileBufAuxSize, x.fileBufAux, GiveTime);
- if (err != noErr) goto exit;
- }
- }
-
- *fileKind = x.fileKind;
-
- exit:
-
- MyDisposePtr(x.fileBuf);
- MyDisposePtr(x.fileBufAux);
- x.fileBuf = nil;
- x.fileBufAux = nil;
- if (err == nntpNoSuchGroupErr || err == nntpNoSuchArticleErr) {
- *fileKind = kArtNotOnServer;
- return noErr;
- } else {
- return NetworkError(gNewsStream, nil, err);
- }
- }
-
-
-
- /*----------------------------------------------------------------------------
- PostArticle
-
- Post an article.
-
- Entry: text = handle to article text, including header.
- statusMsg = status message, C-format.
-
- Exit: function result = error code.
- postIndeterminate = true if entire article sent, but error occured
- or user canceled before final server response received. The
- article may or may not have been posted successfully.
- ----------------------------------------------------------------------------*/
-
- OSErr PostArticle (Handle text, char *statusMsg, Boolean *postIndeterminate)
- {
- OSErr err = noErr;
- NetServerErrInfo serverErrInfo;
-
- err = NntpPostArticle(gNewsStream, text, postIndeterminate);
- if (err == nntpServerErr) {
- NntpGetServerErrInfo(gNewsStream, &serverErrInfo);
- if (serverErrInfo.responseCode != 480) goto exit;
- while (true) {
- err = GetAuthInfo(kPostAuthDlg, statusMsg);
- if (err != noErr) return err;
- ResetNewsServerOptions();
- err = NntpAuthorize(gNewsStream);
- if (err == nntpAuthFailedErr) {
- *gPrefs.authPassword = 0;
- MyICWriteSharedPrefs(kICNewsAuthPassword);
- ErrorMessageNumber(kStrAuthFailed);
- } else if (err != noErr) {
- goto exit;
- } else {
- break;
- }
- }
- err = NntpPostArticle(gNewsStream, text, postIndeterminate);
- if (err != noErr) goto exit;
- } else if (err != noErr) {
- goto exit;
- }
- return noErr;
-
- exit:
-
- return NetworkError(gNewsStream, nil, err);
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetGroupArticleRange
-
- Query the NNTP server to get the current article range for a single group.
-
- Entry: theGroup = pointer to group record.
-
- Exit: function result = error code.
- *groupExits = true if group exists.
-
- theGroup->firstMess = first article in range.
- theGroup->lastMess = last article in range.
- theGroup->numUnread = number of articles in range.
- ----------------------------------------------------------------------------*/
-
- OSErr GetGroupArticleRange (TGroup *theGroup, Boolean *groupExists)
- {
- CStr255 groupName;
- long first, last, count;
- OSErr err = noErr;
-
- *groupExists = true;
- strcpy(groupName, *gGroupNames + theGroup->nameOffset);
- err = NntpGetGroupInfo(gNewsStream, groupName, &first, &last, &count);
- if (err != noErr) goto exit;
-
- if (first == 0 && last == 0) {
- /* Special case empty group: set firstMess = lastMess + 1. */
- theGroup->firstMess = theGroup->lastMess + 1;
- err = AgeArticleCache(groupName, 0x7fffffff);
- if (err != noErr) return err;
- } else {
- if (first <= 0) first = 1;
- if (last < first) last = first - 1;
- theGroup->firstMess = first;
- theGroup->lastMess = last;
- err = AgeArticleCache(groupName, first);
- if (err != noErr) return err;
- }
- theGroup->numUnread = theGroup->lastMess - theGroup->firstMess + 1;
- return noErr;
-
- exit:
-
- if (err == nntpNoSuchGroupErr) {
- *groupExists = false;
- return noErr;
- } else {
- return NetworkError(gNewsStream, nil, err);
- }
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetGroupArrayArticleRanges
-
- Query the NNTP server to get current article range info for designated
- groups in a group list array.
-
- Entry: groupArray = handle to group array.
- numGroups = number of groups in group array.
-
- Each group in the array to be updated is marked with status='x'.
-
- Exit: function result = error code.
- Deleted groups are marked with status='d'.
- ----------------------------------------------------------------------------*/
-
- OSErr GetGroupArrayArticleRanges (TGroup **groupArray, short numGroups)
- {
- NntpGroupInfoHandle info = nil;
- NntpGroupInfoPtr q, qEnd;
- TGroup *p, *pEnd;
- long first, last;
- long numGroupsToUpdate = 0;
- CStr255 groupName;
- OSErr err = noErr;
-
- for (p = *groupArray, pEnd = p + numGroups; p < pEnd; p++)
- if (p->status == 'x') numGroupsToUpdate++;
-
- err = MyNewHandle(numGroupsToUpdate * sizeof(NntpGroupInfo), &info);
- if (err != noErr) goto exit;
-
- for (p = *groupArray, pEnd = p + numGroups, q = *info; p < pEnd; p++) {
- if (p->status == 'x') {
- q->offset = p->nameOffset;
- q++;
- }
- }
-
- err = NntpGetMultipleGroupInfo(gNewsStream, info, numGroupsToUpdate, gGroupNames);
- if (err != noErr) goto exit;
-
- MyHLock(info);
- for (q = *info, qEnd = q + numGroupsToUpdate, p = *groupArray; q < qEnd; q++) {
- while (p->status != 'x') p++;
- if (q->ok) {
- first = q->first;
- last = q->last;
- strcpy(groupName, *gGroupNames + q->offset);
- if (first == 0 && last == 0) {
- /* Special case empty group: set firstMess = lastMess + 1. */
- p->firstMess = p->lastMess + 1;
- err = AgeArticleCache(groupName, 0x7fffffff);
- if (err != noErr) goto exit;
- } else {
- if (first <= 0) first = 1;
- if (last < first) last = first - 1;
- p->firstMess = first;
- p->lastMess = last;
- err = AgeArticleCache(groupName, first);
- if (err != noErr) goto exit;
- }
- p->numUnread = p->lastMess - p->firstMess + 1;
- } else {
- p->status = 'd';
- }
- p++;
- }
- MyDisposeHandle(info);
- return noErr;
-
- exit:
-
- MyDisposeHandle(info);
- return NetworkError(gNewsStream, nil, err);
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetHeaders
-
- Get header lines from the news server.
-
- Entry: groupName = the group name.
- headerName = the header name.
- first = first article number.
- last = last article number.
-
- Exit: function result = error code.
- *headers = handle to array of THeader records, or nil if
- group does not exist.
- *strings = handle to header strings.
- *numHeaders = number of headers.
- ----------------------------------------------------------------------------*/
-
- OSErr GetHeaders (char *groupName, char *headerName, long first, long last,
- THeader ***headers, Handle *strings, long *numHeaders)
- {
- OSErr err;
- NntpHeaderInfoHandle info;
- Handle str;
- long num;
- unsigned char *p, *pEnd, *q;
-
- err = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
- nil, nil, nil, &info, &str, &num);
- if (err != noErr) goto exit;
-
- /* Filter unprintable characters. */
-
- p = (unsigned char*)*str;
- pEnd = p + MyGetHandleSize(str);
- while (p < pEnd) {
- q = p;
- while (*p != 0) {
- if (*p >= ' ') {
- *q++ = *p++;
- } else {
- p++;
- }
- }
- *q = 0;
- p++;
- }
-
- MapLatin1ToMacHandle(str);
-
- *headers = (THeader**)info;
- *strings = str;
- *numHeaders = num;
- return noErr;
-
- exit:
-
- if (err == nntpNoSuchGroupErr) {
- *headers = nil;
- return noErr;
- } else {
- return NetworkError(gNewsStream, nil, err);
- }
- }
-
-
-
- /*----------------------------------------------------------------------------
- SearchHeaders
-
- Search header lines and returns matches. The search is for any
- substring and is case-insensitive. The XPAT command is used if the
- server supports it and the "use XPAT" preference is turned on.
- Otherwise, we do it the hard way.
-
- Entry: groupName = the group name.
- headerName = the header name.
- first = first article number.
- last = last article number.
- pattern = search string. WARNING: This string is modified
- by the function.
-
- Exit: function result = error code.
- *headers = handle to array of THeader records, or nil if
- group does not exist.
- *numHeaders = number of headers.
- ----------------------------------------------------------------------------*/
-
- OSErr SearchHeaders (char *groupName, char *headerName, long first, long last,
- char *pattern, THeader ***headers, short *numHeaders)
- {
- OSErr err;
- NntpHeaderInfoHandle info;
- Handle str = nil;
- long num;
-
- err = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
- pattern, BuildXPAT, MatchPattern, &info, &str, &num);
- if (err != noErr) goto exit;
-
- MyDisposeHandle(str);
- *headers = (THeader**)info;
- *numHeaders = num;
- return noErr;
-
- exit:
-
- MyDisposeHandle(str);
- if (err == nntpNoSuchGroupErr) {
- *headers = nil;
- return noErr;
- } else {
- return NetworkError(gNewsStream, nil, err);
- }
- }
-
-
-
- /*----------------------------------------------------------------------------
- DoGetServerInfo
-
- Handle the "Get Server Info" command.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- OSErr DoGetServerInfo (void)
- {
- char str[1000];
- char *p, *pEnd, *q;
- Handle serverHelpResponse = nil;
- Handle serverInfoText = nil;
- Str255 dateStr, timeStr, versStr, title;
- CStr255 addrStr;
- unsigned long rawSecs;
- unsigned long ipAddr;
- CStr255 helloMsg;
- CStr255 fmt;
- OSErr err = noErr;
- WindowPtr wind;
- NetServerErrInfo serverErrInfo;
-
- MyICReadSharedPrefs(kICNNTPHost);
-
- GetPString(kStrServerInfoWindTitle, title);
- if (CheckTextWindowAlreadyOpen(title)) return noErr;
-
- HiliteMenu(0);
-
- GetDateTime(&rawSecs);
- IUDateString(rawSecs, abbrevDate, dateStr);
- IUTimeString(rawSecs, false, timeStr);
- err = GetVersionString(versStr);
- if (err != noErr) goto exit;
- NntpGetIPAddr(gNewsStream, &ipAddr);
- NntpGetHello(gNewsStream, helloMsg);
- MapLatin1ToMacStr(helloMsg, helloMsg);
- sprintf(addrStr, "%ld.%ld.%ld.%ld", (ipAddr >> 24) & 0xff, (ipAddr >> 16) & 0xff,
- (ipAddr >> 8) & 0xff, ipAddr & 0xff);
- err = NntpGetHelp(gNewsStream, &serverHelpResponse);
- if (err != noErr) goto exit;
- NntpGetServerErrInfo(gNewsStream, &serverErrInfo);
- MapLatin1ToMacStr(serverErrInfo.response, serverErrInfo.response);
- MapLatin1ToMacHandle(serverHelpResponse);
- GetCString(kStrServerInfoFmt, fmt);
- p2cstr(dateStr);
- p2cstr(timeStr);
- p2cstr(versStr);
- p2cstr(gPrefs.newsServerName);
- sprintf(str, fmt, dateStr, timeStr, versStr, gPrefs.newsServerName,
- addrStr, helloMsg, serverErrInfo.response);
- c2pstr((char*)gPrefs.newsServerName);
-
- p = *serverHelpResponse + MyGetHandleSize(serverHelpResponse) - 1;
- while (p > *serverHelpResponse && *p == CR) p--;
- MySetHandleSize(serverHelpResponse, p - *serverHelpResponse + 1);
-
- for (p = *serverHelpResponse, pEnd = p + MyGetHandleSize(serverHelpResponse);
- p < pEnd;
- p++)
- {
- if (*p == '<') {
- q = p+1;
- while (q < pEnd && *q != '>' && *q != '@' && *q != CR) q++;
- if (q < pEnd && *q == '@') {
- q++;
- while (q < pEnd && *q != '>' && *q != CR) q++;
- if (q < pEnd && *q == '>') {
- *p = ' ';
- *q = ' ';
- break;
- }
- }
- }
- }
-
- err = MyPtrToHand(str, &serverInfoText, strlen(str));
- if (err != noErr) goto exit;
- err = MyHandAndHand(serverHelpResponse, serverInfoText);
- if (err != noErr) goto exit;
- MyDisposeHandle(serverHelpResponse);
- serverHelpResponse = nil;
- err = MakeNewTextWindow(title, 0, nil, serverInfoText, &wind);
- if (err != noErr) goto exit;
- MyDisposeHandle(serverInfoText);
- return noErr;
-
- exit:
-
- MyDisposeHandle(serverHelpResponse);
- MyDisposeHandle(serverInfoText);
- return NetworkError(gNewsStream, nil, err);
- }
-
-
-
- /*----------------------------------------------------------------------------
- ResetNewsServerOptions
-
- Reset the server options on the news server stream. This function must
- be called whenever the options change (e.g., when the user changes them
- in the prefs dialog).
- ----------------------------------------------------------------------------*/
-
- void ResetNewsServerOptions (void)
- {
- NntpStreamOptions options;
-
- SetServerOptions(&options);
- if (gNewsStream != nil) NntpSetStreamOptions(gNewsStream, &options);
- }
-
-
-
- /*----------------------------------------------------------------------------
- Reauthenticate
-
- Reauthenticate the user after a change in username or password.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- OSErr ReAuthenticate (void)
- {
- NntpStreamRef oldStream;
- OSErr err = noErr;
-
- if (gNewsStream == nil) return noErr;
- if (!gPrefs.authAtStartup) {
- NntpAbort(gNewsStream);
- } else {
- oldStream = gNewsStream;
- gNewsStream = nil;
- err = StartNNTP();
- if (err == noErr) {
- NntpClose(oldStream);
- } else {
- gNewsStream = oldStream;
- }
- }
- return err;
- }
-